package org.altervista.cp.thinjdbc.wrappers;

import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.util.LinkedList;
import java.util.Map;

import org.altervista.cp.thinjdbc.util.LogHelper;

public abstract class Connection extends Wrapper<java.sql.Connection> implements
		java.sql.Connection {

	private static final LogHelper logger = new LogHelper(Connection.class);

	static Connection newInstance(java.sql.Connection c, DataSource dsw) {
		Connection cw = _GENERATOR.generate(c);
		cw._dsw = dsw;
		return cw;
	}

	public void close() {
		if (_statementsCreators.size() > 0) {
			logger.debug("closing {0} statements", _statementsCreators.size());
		}
		for (StatementWrapperCreator swc : _statementsCreators) {
			try {
				swc._sw.close();
			} catch (SQLRuntimeException e) {
			}
		}
		_statementsCreators.clear();

		try {
			java.sql.Connection c = unwrap();
			if (c != null) {
				logger.debug("closing connection");
				c.close();
			}
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		} finally {
			if (_dbmdw != null) {
				_dbmdw.close();
				_dbmdw = null;
			}
			_dsw = null;
			_rewrapCount = 0;
			_GENERATOR.release(this);
		}
	}

	public DatabaseMetaData getMetaData() {
		try {
			if (_dbmdw == null) {
				_dbmdw = DatabaseMetaData.newInstance(unwrap().getMetaData());
			}
			_dbmdw.setConnectionWrapper(this);
			return _dbmdw;
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		}
	}

	public Statement createStatement() {
		return new StatementWrapperCreator(null, null, null, null, null, null,
				null, null).create();
	}

	public Statement createStatement(int resultSetType,
			int resultSetConcurrency, int resultSetHoldability) {
		return new StatementWrapperCreator(null, null, resultSetType,
				resultSetConcurrency, resultSetHoldability, null, null, null)
				.create();
	}

	public Statement createStatement(int resultSetType, int resultSetConcurrency) {
		return new StatementWrapperCreator(null, null, resultSetType,
				resultSetConcurrency, null, null, null, null).create();
	}

	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability) {
		return (CallableStatement) new StatementWrapperCreator(sql, null,
				resultSetType, resultSetConcurrency, resultSetHoldability,
				null, null, null).create();
	}

	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency) {
		return (CallableStatement) new StatementWrapperCreator(sql, null,
				resultSetType, resultSetConcurrency, null, null, null, null)
				.create();
	}

	public CallableStatement prepareCall(String sql) {
		return (CallableStatement) new StatementWrapperCreator(sql, null, null,
				null, null, null, null, null).create();
	}

	public PreparedStatement prepareStatement(String sql) {
		return (PreparedStatement) new StatementWrapperCreator(null, sql, null,
				null, null, null, null, null).create();
	}

	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) {
		return (PreparedStatement) new StatementWrapperCreator(null, sql, null,
				null, null, autoGeneratedKeys, null, null).create();
	}

	public PreparedStatement prepareStatement(String sql, int[] columnIndexes) {
		return (PreparedStatement) new StatementWrapperCreator(null, sql, null,
				null, null, null, columnIndexes, null).create();
	}

	public PreparedStatement prepareStatement(String sql, String[] columnNames) {
		return (PreparedStatement) new StatementWrapperCreator(null, sql, null,
				null, null, null, null, columnNames).create();
	}

	public PreparedStatement prepareStatement(String sql, int resultSetType,
			int resultSetConcurrency) {
		return (PreparedStatement) new StatementWrapperCreator(null, sql,
				resultSetType, resultSetConcurrency, null, null, null, null)
				.create();
	}

	public PreparedStatement prepareStatement(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability) {
		return (PreparedStatement) new StatementWrapperCreator(null, sql,
				resultSetType, resultSetConcurrency, resultSetHoldability,
				null, null, null).create();
	}

	public abstract void clearWarnings();

	public abstract void commit();

	public abstract boolean getAutoCommit();

	public abstract String getCatalog();

	public abstract int getHoldability();

	public abstract int getTransactionIsolation();

	public abstract Map<String, Class<?>> getTypeMap();

	public abstract SQLWarning getWarnings();

	public abstract boolean isClosed();

	public abstract boolean isReadOnly();

	public abstract String nativeSQL(String sql);

	public abstract void releaseSavepoint(Savepoint savepoint);

	public abstract void rollback();

	public abstract void rollback(Savepoint savepoint);

	public abstract void setAutoCommit(boolean autoCommit);

	public abstract void setCatalog(String catalog);

	public abstract void setHoldability(int holdability);

	public abstract void setReadOnly(boolean readOnly);

	public abstract Savepoint setSavepoint();

	public abstract Savepoint setSavepoint(String name);

	public abstract void setTransactionIsolation(int level);

	public abstract void setTypeMap(Map<String, Class<?>> map);

	DataSource getDataSourceWrapper() {
		return _dsw;
	}

	Statement rewrap(SQLException e) {
		if (_rewrapCount++ < _dsw.getRetriesOnError()) {
			logger
					.debug(
							"Rewrapping connection (try nr. {0}) because of the following SQL error",
							e, _rewrapCount);
			_dsw.rewrap(this);
			return _statementsCreators.getLast().create();
		} else {
			throw new SQLRuntimeException(e);
		}
	}

	private class StatementWrapperCreator {
		private StatementWrapperCreator(String sqlCall, String sqlPrepared,
				Integer resultSetType, Integer resultSetConcurrency,
				Integer resultSetHoldability, Integer autoGeneratedKeys,
				int[] columnIndexes, String[] columnNames) {
			_sqlCall = sqlCall;
			_sqlPrepared = sqlPrepared;
			_resultSetType = resultSetType;
			_resultSetConcurrency = resultSetConcurrency;
			_resultSetHoldability = resultSetHoldability;
			_autoGeneratedKeys = autoGeneratedKeys;
			_columnIndexes = columnIndexes;
			_columnNames = columnNames;

			_statementsCreators.add(this);
			System.out.println();
		}

		private String _sqlCall;
		private String _sqlPrepared;
		private Integer _resultSetType;
		private Integer _resultSetConcurrency;
		private Integer _resultSetHoldability;
		private Integer _autoGeneratedKeys;
		private int[] _columnIndexes;
		private String[] _columnNames;

		private Statement _sw;

		Statement create() {
			java.sql.Statement s = null;
			try {
				if (_sqlCall != null) {
					if (_resultSetHoldability == null) {
						if (_resultSetConcurrency == null) {
							s = unwrap().prepareCall(_sqlCall);
						} else {
							s = unwrap().prepareCall(_sqlCall, _resultSetType,
									_resultSetConcurrency);
						}
					} else {
						s = unwrap().prepareCall(_sqlCall, _resultSetType,
								_resultSetConcurrency, _resultSetHoldability);
					}
				} else if (_sqlPrepared != null) {
					if (_autoGeneratedKeys != null) {
						s = unwrap().prepareStatement(_sqlPrepared,
								_autoGeneratedKeys);
					} else if (_columnIndexes != null) {
						s = unwrap().prepareStatement(_sqlPrepared,
								_columnIndexes);
					} else if (_columnNames != null) {
						s = unwrap().prepareStatement(_sqlPrepared,
								_columnNames);
					} else if (_resultSetHoldability == null) {
						if (_resultSetConcurrency == null) {
							s = unwrap().prepareStatement(_sqlPrepared);
						} else {
							s = unwrap().prepareStatement(_sqlPrepared,
									_resultSetType, _resultSetConcurrency);
						}
					} else {
						s = unwrap().prepareStatement(_sqlPrepared,
								_resultSetType, _resultSetConcurrency,
								_resultSetHoldability);
					}
				} else {
					if (_resultSetHoldability == null) {
						if (_resultSetConcurrency == null) {
							s = unwrap().createStatement();
						} else {
							s = unwrap().createStatement(_resultSetType,
									_resultSetConcurrency);
						}
					} else {
						s = unwrap().createStatement(_resultSetType,
								_resultSetConcurrency, _resultSetHoldability);
					}
				}
			} catch (SQLException e) {
				rewrap(e);
			}
			if (_sw == null) {
				if (s instanceof java.sql.CallableStatement) {
					_sw = CallableStatement.newInstance(
							(java.sql.CallableStatement) s, _sqlCall);
				} else if (s instanceof java.sql.PreparedStatement) {
					_sw = PreparedStatement.newInstance(
							(java.sql.PreparedStatement) s, _sqlPrepared);
				} else {
					_sw = Statement.newInstance(s);
				}
			} else {
				try {
					_sw.unwrap().close();
				} catch (SQLException e) {
				}
				_sw.wrap(s);
			}
			_sw.setConnectionWrapper(Connection.this);
			return _sw;
		}
	}

	public int _rewrapCount;
	private DatabaseMetaData _dbmdw;
	private DataSource _dsw;
	private LinkedList<StatementWrapperCreator> _statementsCreators = new LinkedList<StatementWrapperCreator>();

	private static final _ProxyGenerator<Connection> _GENERATOR = new _ProxyGenerator<Connection>(
			Connection.class);

}
